implement polymorphism using composition

Polymorphism can be effectively implemented using composition by creating classes that contain instances of other classes, allowing for flexible and reusable code without the tight coupling of inheritance.

Understanding Composition

Composition is a design principle in object-oriented programming where a class is composed of one or more objects from other classes. This allows for building complex types by combining simpler ones, promoting code reuse and flexibility. Unlike inheritance, which creates a tight coupling between parent and child classes, composition allows for a more modular approach where components can be easily replaced or modified without affecting other parts of the system.

Achieving Polymorphism with Composition

Define Interfaces: Start by defining interfaces that declare the methods you want to implement. This allows different classes to provide their own implementations of these methods.

public interface Shape {
    void draw();
}

Implement Classes: Create classes that implement the interface. Each class can have its own unique behavior while adhering to the same interface.

public class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing a Circle");
    }
}
public class Square implements Shape {
    public void draw() {
        System.out.println("Drawing a Square");
    }
}

Use Composition: Create a class that uses composition to include instances of the interface type. This class can then call the methods on the composed objects, achieving polymorphic behavior.

public class Drawing {
    private List<Shape> shapes = new ArrayList<>();
    public void addShape(Shape shape) {
        shapes.add(shape);
    }
    public void drawAll() {
        for(Shape shape : shapes) {
            shape.draw(); // Polymorphic call
        }
    }
}

Demonstrate Usage: You can now create a Drawing object, add different shapes to it, and call the drawAll method to see polymorphism in action.

public class Main {
    public static void main(String[] args) {
        Drawing drawing = new Drawing();
        drawing.addShape(new Circle());
        drawing.addShape(new Square());
        drawing.drawAll(); // Outputs: Drawing a Circle, Drawing a Square
    }
}

Advantages of Composition for Polymorphism

Loose Coupling: Composition allows for loosely coupled designs, making it easier to change or replace components without affecting the entire system.

Flexibility: You can easily add new behaviors by creating new classes that implement the same interface, without modifying existing code.

Reusability: Components can be reused across different classes, promoting code reuse and reducing redundancy.

By using composition, you can achieve polymorphic behavior while maintaining a clean and flexible code structure, making it a preferred approach in many scenarios.